#include "app_admin.h"
#include "request.h"
#include "http_server.h"
#include "json/json.h"

typedef router_family*(*p_create_app)(void);  
typedef void(*p_set_logging_instance)(logging&);

#ifdef WIN32

#define dll_ex ".dll"
#define open_dll(dll) LoadLibraryA(dll)
#define get_proc(hdll, func) GetProcAddress(hdll, func)
#define close_dll(dll) FreeLibrary((HINSTANCE)dll)
#define dll_error() GetLastError()
#else

#include <dlfcn.h> 

#define dll_ex ".so"
#define open_dll(dll) dlopen(dll, RTLD_LAZY)
#define get_proc(hdll, func) dlsym(hdll, func)
#define close_dll(dll) dlclose(dll)
#define dll_error() dlerror()

#endif // WIN32

app_admin::app_admin() :
router_family("admin", boost::regex("^/app/"))
{
	regist_router(new router_child("action", boost::regex("^$"), [this](const request& req, response& res)
	{
		render_app_list(res);
	}));

	regist_router(new router_child("action", boost::regex("^action/"), [this](const request& req, response& res)
	{
		Json::Reader reader;
		Json::Value json_val;
		if (!reader.parse(req._body, json_val))
			return render_app_list(res);

		auto action = json_val["action"].asString();
		if (!action.compare("load-app"))
		{
			auto app_path = json_val["args"].asString();
			load_app(app_path);
		}
		else if(!action.compare("manage"))
		{
			auto& args = json_val["args"];
			for (auto it = args.begin(); it != args.end(); ++it)
			{
				auto app_name = (*it)["name"].asString();
				auto action = (*it)["action"].asString();
				if (!action.compare("unload"))
					unload_app(app_name);
				else if(!action.compare("reload"))
					reload_app(app_name);
			}
		}
		render_app_list(res);
	}));
}

bool app_admin::is_app_registered(const std::string& app_path)
{
	std::lock_guard<std::mutex> lock(_mutex);
	for (auto& app : _app_map)
	{
		if (!app.second._app_path.compare(app_path))
			return true;
	}
	return false;
}

void app_admin::register_app(const std::string& app, const std::string& path, void* hDLL)
{
	app_info ainfo;
	ainfo._app_name = app;
	ainfo._app_path = path;
	ainfo._h_dll = hDLL;

	std::lock_guard<std::mutex> lock(_mutex);
	_app_map.insert(std::make_pair(app, ainfo));
}

void* app_admin::delete_registered_app(const std::string& app)
{
	std::lock_guard<std::mutex> lock(_mutex);

	void* hDll = NULL;
	auto it = _app_map.find(app);
	if (it != _app_map.end())
	{
		hDll = it->second._h_dll;
		_app_map.erase(it);
	}
	return hDll;
}

std::string app_admin::get_app_path(const std::string& app)
{
	std::lock_guard<std::mutex> lock(_mutex);
	auto it = _app_map.find(app);
	if (it != _app_map.end())
		return it->second._app_path;
	return "";
}

void app_admin::render_app_list(response& res)
{
	Json::Value ctx;
	{
		std::lock_guard<std::mutex> lock(_mutex);
		for (auto& app : _app_map)
		{
			Json::Value json_app;
			json_app["app_name"] = app.second._app_name;
			json_app["app_path"] = app.second._app_path;

			ctx["app_list"].append(json_app);
		}
	}
	res.render(200, ctx);
}

void app_admin::load_app(std::string& app_path)
{
	if (is_app_registered(app_path))
		return;

	auto hDLL = open_dll(app_path.c_str());  //load .dll(.so)

	if(hDLL != NULL)
	{
		auto create_app = (p_create_app)get_proc(hDLL, "create_app");
		auto set_logging_instance = (p_set_logging_instance)get_proc(hDLL, "set_logging_instance");
		if (create_app && set_logging_instance)
		{
			auto app = create_app();
			_http_server->register_router(app);
			LOGI("register app router [ %s ]", app->_family);
			set_logging_instance(logger);
			register_app(app->_family, app_path, hDLL);
		}
		else
		{
			close_dll(hDLL);
			LOGE("get_proc(hDLL, \"%s\") == NULL or get_proc(hDLL, \"%s\")", "create_app", "set_logging_instance");
		}
	}
	else
	{
		LOGE("open_dll(\"%s\") == NULL, reason: %s", app_path, dll_error());
	}
}

void app_admin::unload_app(std::string& app_name)
{
	if (!app_name.compare(_family))
	{
		LOGE("error: try to delete admin router");
		return;
	}
	if (_http_server->delete_router(app_name.c_str()))
	{
		LOGI("delete app router [ %s ]", app_name);
	}
	auto hDll = delete_registered_app(app_name);
	if (hDll)
	{
		close_dll(hDll);
	}
}

void app_admin::reload_app(std::string& app_name)
{
	auto app_path = get_app_path(app_name);
	if (app_path.empty())
		return;
	unload_app(app_name);
	load_app(app_path);
}
